#!/usr/bin/python

# server.py
import binascii
import socket
import struct
import time
import traceback
import sys
import encryption_util
import sqlalchemy
import os
import commands
from multiprocessing import Process, Lock
from encryption_util import *
from M2Crypto import *
from sqlalchemy import *
from xml.dom.minidom import *
from xml.dom import minidom, Node
from time import strftime
import threading

from binascii import hexlify, unhexlify

#===================     Global definitions Section ======================================

# CA - client Definitions 
# Authentication
X509REQ = "XREQ"
OCSP = "OCSP"
REGISTER = "REGI"

STATUS = "STATUS" # Status Query
CALINE = "LINE" #Direct Connection to CA
FAIL = "FAIL" #Failure Signal

DEFAULT = "0000"


CERT_DIR="./certificate/"
CERT_FILE="./certificate/ca"
CERT_INDEX="./certificate/index.txt"

####################################################################################
########################### UTILITIES ##############################################
####################################################################################


def btdebug( msg ):
    """ Prints a messsage to the screen with the name of the current thread """
    print "[%s] %s" % ( str(threading.currentThread().getName()), msg )


# take a string with the name and creates the authority
def _certificate_create(file , serverEmail ):
        cert_name = X509.X509_Name()
        cert_name.C = "GR"
        cert_name.ST = "Crete"
        cert_name.L = "Herakleion"
        cert_name.O = "UoC"
        cert_name.CN = "139.91.70.38"
        cert_name.OU = "CSD"
        cert_name.Email = serverEmail
        ca_cert, ca_pubkey, ca_privkey, ca_ekey = create_certificate_authority(file=file,cert_name=cert_name)  
        ca_cert.save(file+".pem")


####################################################################################
########################### Handlers  ##############################################
####################################################################################


# if something went wrong
def _default_hdlr(peerconn, msgdata, ca_cert, ca_ekey, db) :
    print(peerconn.clientInfo(), msgdata)
    
#-------------------------------------------------------------------------

# register as active node
def _line_hdlr(peerconn, msgdata, ca_cert, ca_ekey, db) :

    metadata = MetaData(db)
    clientListenPort = str(msgdata).split(' ')[0];
    clientName = str(msgdata).split(' ')[1];
    host = peerconn.clientInfo()[0]
    securePort = peerconn.clientInfo()[1]
    table_nodes = Table('nodes', metadata, autoload=True)
    # get certificate from client with non-direct method! - FIXME
    #ctx = SSL.Context('sslv3')
    #s = SSL.Connection(ctx)
    #s.connect(( host, int (port) ))
    #s = peerconn.clientSocket()
    #print ("Verify : ", s.get_peer_cert())
       
    username = clientName;
  
    ins = table_nodes.insert()

    # inser node in database 
    ins.execute(  username=username, ip=host,port=int(clientListenPort) , secureline=int(securePort));

    return _emergency_hdlr (peerconn, None, ca_cert, ca_ekey, db)
    
#-------------------------------------------------------------------------


# handling x509 requests
def _x509req_hdlr(peerconn, msgdata, ca_cert, ca_ekey, db ):
	btdebug( 'Trying to create X509 certificate for Client:' + peerconn.clientInfo()[0] )
	try :
            ##############################################################
            # this is bad however from m2crypto<0.20.0 does not support load_request_string
            req_fname = get_secure_filename()
            f = open(req_fname, 'w')
            f.write(msgdata)
            f.close()
            req=X509.load_request(req_fname)      
            os.unlink(req_fname)
            # this works normally
            #req=X509.load_request_string(msgdata, 1)
            ###########################################
            
            # Get pubkey
            ckey=req.get_pubkey()
            
            if( req.verify(ckey) ):
                btdebug( 'Certificate public key sing verification passed for client:' + peerconn.clientInfo()[0] )
            else:
                btdebug( 'Certificate public key check verification failed from client:' + peerconn.clientInfo()[0] )
                return

            # now create the certificate 
            client_cert = create_certificate(req, (ca_cert, ca_ekey) , 1, (now(), now())  );
            # convert certificate to pem format
            msgdata=client_cert.as_pem() 
            # send it back to client
            peerconn.senddata(msgdata)

            btdebug( 'Created X509 certificate for Client:' + peerconn.clientInfo()[0] )

	    metadata = MetaData(db)
	    #get the table of nodes 
	    certs = Table('certificates', metadata, autoload=True)
	    cert_data=(client_cert.as_pem()).lstrip('-----BEGIN CERTIFICATE-----\n').rstrip('\n-----END CERTIFICATE-----')
	    ins = certs.insert().values( data=cert_data )
	    ins.execute()


	except:
		print "=============== Something went wrong! ===============\n"
	        btdebug( 'The message was from:' + peerconn.clientInfo()[0] )
		traceback.print_exc()

#return True



# Check if certificate exists in revocation list (public keys) 
def _ocsp_hdlr(peerconn, msgdata, ca_cert, ca_ekey, db) :

    btdebug( 'Trying to answer X509ocsp req for Client:' + peerconn.clientInfo()[0] )
    try:


	# strip data
        cert_data=msgdata.lstrip('-----BEGIN CERTIFICATE-----\n').rstrip('\n-----END CERTIFICATE-----')
        
        metadata = MetaData(db)
	#get the table 
	certs = Table('certificates', metadata, autoload=True)
        ex = certs.select(certs.c.data == cert_data)
        res = ex.execute()
        for row in res:
            if row['valid'] == True :
                peerconn.senddata("TRUE")
            else:
                peerconn.senddata("FALSE")
                

        # if not found                  
        peerconn.senddata("UNKNOWN")


    except :
        traceback.print_exc()

# When a node wants to push data in the server
def _emergency_hdlr(peerconn, msgdata, ca_cert, ca_ekey, db) :

    while ( True ) : 
        try :
            print "Waiting here"
            msgdata , msgtype= peerconn.recvdata()
            if (msgdata , msgtype) == (None , None) :
                #peerconn.close()
                return False

            print "\n----------- EMERGENCY SIGNAL FROM ----------\n  : " ,  peerconn.clientInfo() , msgdata
            curr_time=strftime("%Y-%m-%d %H:%M:%S")

            ## Database
            metadata = MetaData(db)
            # get the table
            t_nodes = Table('nodes', metadata, autoload=True)

            ip = peerconn.clientInfo()[0]
            port = peerconn.clientInfo()[1] 

            update = t_nodes.update().where( t_nodes.c.secureline==port).where( t_nodes.c.ip==ip ).values(emergency=curr_time) 
            update.execute();


        except :
            print "Shutting down emergency channel"
            #peerconn.close()
            traceback.print_exc()
            return False
        
        


# Authentication
def _registerPass_hdlr ( peerconn,  msgdata, ca_cert, ca_ekey, db ) :

    ## FWTH: FIXME: edw 8a eprepe na epistrefei to certificate alla den to epistrefei...
    #print ">>>>> " + str(peerconn.get_peer_certificate()) #get_subject().Email ;


    # estw  oti pernw to name sto username
    print "Received password ", msgdata

    res=msgdata.rsplit(" "); 
    username = res[0]
    password = res[1]

    #calculate the hash
    passwd= hexlify( EVP.hmac(username, password, algo='sha256')) 

    ## Database
    metadata = MetaData(db)
    # get the table
    t_users = Table('users', metadata, autoload=True)

    #xm...
    db.echo = False
 
    ins = t_users.insert((username,passwd, "","" ))
    try: 
        # try to insert new username / password
        ins.execute()
    except :
        # if faild then: username exists (insert faild -> username is primary key) so update key 
        try:
            update = t_users.update().where(t_users.c.username==username).values(passwd=passwd) 
            update.execute();
        except:
            peerconn.senddata("FAILED")
            return;

    peerconn.senddata("OK")







###############################################################################
#==============================================================================
#==============================================================================


class caServer:
    """ Implements the core functionality of web server """

    #--------------------------------------------------------------------------
    def __init__( self, certfile, maxpeers=16, serverport=11224, myid=None, serverhost = None ):
    #--------------------------------------------------------------------------
        """ Initializes a peer worker with the ability to catalog
        information for up to maxpeers number of peers, listening on
        a given server port , with a given canonical peer name (id)
        and host address. If not supplied, the host address
        (serverhost) will be determined by attempting to connect to an
        Internet host like Google     """
        self.debug = 1
        self.file=certfile
        self.maxpeers = int(maxpeers)
        self.serverport = int(serverport)

        if serverhost: self.serverhost = serverhost
        else: self.__initserverhost()
        if myid: self.myid = myid
        else: self.myid = (self.serverhost, self.serverport)
    
        self.peerlock = Lock()  # ensure proper access to
                                    # peers list (maybe better to use
                                    # threading.RLock (reentrant))

        self.shutdown = False  # used to stop the main loop
    
        # Mapping Codes to Handlers
	    
        self.handlers = {
	    DEFAULT : _default_hdlr ,
	    X509REQ : _x509req_hdlr ,
	    OCSP    : _ocsp_hdlr,
            FAIL :_emergency_hdlr,
            REGISTER : _registerPass_hdlr,
            CALINE :  _line_hdlr
        }


        askForPass()
        if not os.path.exists( CERT_DIR ) :
            os.makedirs(CERT_DIR)

	    if not ( os.path.exists(CERT_FILE+".pem") or os.path.exists(CERT_FILE+".pem")):

		    print "Keyfile/certificate not found creating new key "
		    serverEmail = raw_input("Give owner's email :\n")
            _certificate_create( CERT_FILE, serverEmail )
       
        self.ca_cert, self.ca_pubkey, self.ca_privkey, self.ca_ekey = load_certificate_authority(file=CERT_FILE)
        self.db = create_engine('mysql://apache:apache@localhost/p2p')

        metadata = MetaData(self.db)

        # get the table of nodes 
        nodes = Table('nodes', metadata, autoload=True)
        #now delete everything from nodes
        nodes.delete().execute()


        """ Server should load list of peers from Database """

    #--------------------------------------------------------------------------
    def __initserverhost( self ):
    #--------------------------------------------------------------------------
        s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        s.connect( ( "www.google.com", 80 ) )
        self.serverhost = s.getsockname()[0]
        s.close()

    def __debug( self, msg ):
    #--------------------------------------------------------------------------
        #if self.debug:
        btdebug( msg )
        


    #--------------------------------------------------------------------------
    def __handlepeer( self, clientsock ):
    #--------------------------------------------------------------------------
        """
        handlepeer( new socket connection ) -> ()
    
        Dispatches messages from the socket connection
        """
    
        self.__debug( 'New child ' + str(threading.currentThread().getName()) )
        self.__debug( 'Connected ' + str(clientsock.getpeername()) )
    
        host, port = clientsock.getpeername()
        peerconn = BTPeerSecureConnection( None, host, port, clientsock, debug=False, file=CERT_FILE )


        try:
           msgdata, msgtype = peerconn.recvdata()
           # If it's X509REQ should not be treated like xml Form
           print (" msgType " , msgtype )

           if msgtype: msgtype = msgtype.upper()           
           if msgtype not in self.handlers:
               self.__debug( 'Not handled: %s: %s' % (msgtype, msgdata) )
           else:
               self.__debug( 'Handling peer msg: %s' % msgtype )
           self.handlers[ msgtype ]( peerconn, msgdata, self.ca_cert, self.ca_ekey, self.db )
   



        #----- Database - CALINE issues --------
           if msgtype != CALINE :
               self.__debug( 'Disconnecting ' + str(clientsock.getpeername()) )
               peerconn.close()
           else :
               #delete node from database
               print "Delete from database and close connection : " , str(clientsock.getpeername())

               clientIp = clientsock.getpeername()[0]
               securePort = clientsock.getpeername()[1] #Emergency'

               db = create_engine('mysql://apache:apache@localhost/p2p')
               metadata = MetaData(db)
               table_nodes = Table('nodes', metadata, autoload=True)

               res = table_nodes.delete( "secureline=\"" + str(securePort) + "\"")
               res.execute()
               print "Deleted cmd : ", res
               peerconn.close()
        #-------------------------------------------


        except KeyboardInterrupt:
            peerconn.close()
            raise
        except:
            if self.debug:
                traceback.print_exc()
                return False


        # end handlepeer method


    #--------------------------------------------------------------------------
    def makesecureserversocket( self, port, file, backlog=5 ):
    #--------------------------------------------------------------------------
        """ Constructs and prepares a secure server socket listening on the given 
        port.
    
        """
        ctx = SSL.Context('sslv3')
        certfile= file+".pem"
        keyfile=file+".key"
        ctx.load_cert( certfile , keyfile, call)
        bindsocket = SSL.Connection(ctx)
        bindsocket.bind(('', port))
        bindsocket.listen(backlog)
        #s, fromaddr = bindsocket.accept()

        return bindsocket
    

    #--------------------------------------------------------------------------
    def connectandsend( self, host, port, msgdata,  pid=None, waitreply=True ):
    #--------------------------------------------------------------------------
        """
        connectandsend( host, port, message type, message data, peer id,
        wait for a reply ) -> [ ( reply type, reply data ), ... ]
    
        Connects and sends a message to the specified host:port. The host's
        reply, if expected, will be returned as a list of tuples.
    
        """
        msgreply = []
        try:
            peerconn = BTPeerSecureConnection( pid, host, port, debug=self.debug )

            peerconn.senddata( msgdata )
            #self.__debug( 'Sent %s: %s' % (pid, msgtype) )
            
            if waitreply:
                onereply, msgtype = peerconn.recvdata()

            while (onereply !=  None ):
                msgreply.append( onereply )
                self.__debug( 'Got reply : %s'  %  str(msgreply) )
                onereply, msgtype = peerconn.recvdata()
            peerconn.close()
        except KeyboardInterrupt:
            raise
        except:
            if self.debug:
                traceback.print_exc()
        
        return msgreply
    
        # end connectsend method

    #--------------------------------------------------------------------------
    def mainloop( self ):
    #--------------------------------------------------------------------------

        s=self.makesecureserversocket(self.serverport,"certificate/ca")
        self.__debug( 'Server started: (%s:%d)' % (  self.serverhost, self.serverport ) )
        while not self.shutdown:
            try:
                self.__debug( 'Listening for connections...' )
                clientsock, clientaddr = s.accept()

                t = threading.Thread( target = self.__handlepeer,
                              args = [ clientsock ] )
                t.start()
               
            except KeyboardInterrupt:
                print 'KeyboardInterrupt: stopping mainloop'
                self.shutdown = True
                #sys.exit(0)
            except:
                if self.debug:
                    traceback.print_exc()
                    sys.exit(0)
    
        # end while loop
        self.__debug( 'Main loop exiting' )
    
        s.close()
	# Don't like it but... no other solution atm
   	sys.exit(0)
    
        # end mainloop method


# end webServer class


# **********************************************************




class BTPeerSecureConnection:

    #--------------------------------------------------------------------------
    def __init__( self, peerid, host, port, sock=None, debug=False, file=None ):
    #--------------------------------------------------------------------------
    # any exceptions thrown upwards

        self.id = peerid
        self.debug = debug
        try:
            if not sock:

                ctx = SSL.Context('sslv3')
                if (file != None):
                    certfile= file+".pem"
                    keyfile=file+".key"
                    ctx.load_cert( certfile, keyfile, call )
                self.s = SSL.Connection(ctx)
                #self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
                self.s.connect(( host, int (port) ))

            else:
                self.s = sock

            self.sd = self.s.makefile( 'rw', 0 )

        except:
            if self.debug:
                traceback.print_exc()
                sys.exit(0)    
    #--------------------------------------------------------------------------
    def __makemsg( self, msgtype, msgdata ):
    #--------------------------------------------------------------------------
        msglen = len(msgdata)
        msg = struct.pack( "!4sL%ds" % msglen, msgtype, msglen, msgdata )
        return msg

    #--------------------------------------------------------------------------
    def clientInfo( self ):
    #--------------------------------------------------------------------------    
        return (self.s.getpeername())

    #--------------------------------------------------------------------------
    def clientSocket( self ):
    #--------------------------------------------------------------------------    
        return (self.s)


    #--------------------------------------------------------------------------
    def __debug( self, msg ):
    #--------------------------------------------------------------------------
        if self.debug:
            btdebug( msg )

    #--------------------------------------------------------------------------
    def get_peer_certificate( self):
    #--------------------------------------------------------------------------
        return self.s.get_peer_cert()


    #--------------------------------------------------------------------------
    def senddata( self,  msgdata , msgtype="NONE" ):
    #--------------------------------------------------------------------------
        """
        senddata( message type, message data ) -> boolean status
    
        Send a message through a peer connection. Returns True on success
        or False if there was an error.
        """
    
        try:
            msg = self.__makemsg( msgtype, msgdata )
            self.sd.write( msg )
            self.sd.flush()
        except KeyboardInterrupt:
            raise "KeyboardInterrupt"
        except:
            if self.debug:
                traceback.print_exc()
            return False
        return True


    #--------------------------------------------------------------------------
    def recvdata( self ):
    #--------------------------------------------------------------------------
        """
        recvdata() -> (msgtype, msgdata)
    
        Receive a message from a peer connection. Returns (None, None)
        if there was any error.
        """
        try:
            msgtype = self.sd.read( 4 )
            #if not msgtype: return (None, None)
                
            lenstr = self.sd.read( 4 )
            msglen = int(struct.unpack( "!L", lenstr )[0])
            msg = ""
            while len(msg) != msglen:
                data = self.sd.read( min(2048, msglen - len(msg)) )
                if not len(data):
                    break
                msg += data
    
            if len(msg) != msglen:
                return None, None
    
        except KeyboardInterrupt:
            raise
        except:
            if self.debug:
                traceback.print_exc()
            return None, None
    
        return  msg , msgtype
    
        # end recvdata method


    #--------------------------------------------------------------------------
    def close( self ):
    #--------------------------------------------------------------------------
        """
        close()
    
        Close the peer connection. The send and recv methods will not work
        after this call.
        """
    
        self.s.close()
        self.s = None
        self.sd = None


    #--------------------------------------------------------------------------
    def __str__( self ):
    #--------------------------------------------------------------------------
        return "|%s|" % peerid




####################################################################################
########################### Main ###################################################
####################################################################################
if __name__ == "__main__" :

    try :
        #serverPort = int(input('Which port ? : '))

        s = caServer("certificate/ca")

        t = threading.Thread( target = s.mainloop)
        t.start()  


        print "Key loaded"

        while True :
            sel = input("1) Exit \n")
            if sel == 1 :
                sys.exit("Exiting")
            else :
                print "Something goes terribly wrong"
                continue

    except KeyboardInterrupt:
        print 'KeyboardInterrupt: stopping mainloop'
        sys.exit(0)
    except:
        traceback.print_exc()
        sys.exit("Terminating server......")


